using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using VerticalProcesses;
using LateralProcesses;


namespace Tester
{
    class Program
    {
        static void Main(string[] args)

        {           
            //initialze model run
            //string configFile = "C:\\models\\PTModel\\input\\YAM-config.txt";
            //string configFile = "C:\\models\\PTModel\\input-lys\\YAM-configfile-lys.txt";
            string configFile = "C:\\Projects\\williLake\\Yam\\input\\YAM-config.txt";

            //**************************************************************************************
            //construction
            //**************************************************************************************

            Configuration config = new Configuration(configFile);
            ClimateForcing ClimateForcing = new ClimateForcing(config.Metfiles, config.TimeSpan,config.TimeStepHrs,config.GruGlobalParameters["tt"],
                config.GruGlobalParameters["tti"],config.EvapoEq);


            GRUBuilder grus = new GRUBuilder(config.GRUInfoFile, config.GruGlobalParameters, config.TimeStepHrs,config.EvapoEq, config.SnowEngergyInputEq, config.CanopyType, config.SnowpackType, config.SoilType);


            // these null instances need to be here in case the user wants to run the gru processes only
            CDUBuilder cdus = null;
            OutputWriter output = null;

            if (!(bool)config.GruOnly)
            {
                cdus = new CDUBuilder(config.CDUInfoFile, config.Routing,config.CduGlobalParameters, config.TimeStepHrs);


                ////establish Cdu routing order

                ////this is a little overshoot here since we would need to loop only 
                ////until all cdus have an order. i should use 'exists' here and check
                //// for 0 in cdus.RouteOrder...will do that once i know where this finally goes...
                //// is would be nice to use this with an implementation of IComparable,
                //// but dunno of this works with multiple loops 
                int routeIndex = 1;
                int[] routeOrder = new int[cdus.CDUs.Count];
                for (int h = 0; h < cdus.CDUs.Count; h++)
                {
                    for (int i = 0; i < routeOrder.Length; i++)
                    {

                        if (routeOrder[i] == 0)
                        {
                            int routeNr = 1;

                            // look if cdu receives inflow from another cdu
                            // exclude cdus with existing order (&& statement)
                            for (int j = 0; j < routeOrder.Length; j++)
                            {
                                if (cdus.CDUs[i].CDUID == cdus.CDUs[j].CDUIdDown && routeOrder[j] == 0)
                                {
                                    routeNr = 0;
                                }
                            }
                            //cdu gets no inflow from any other cdu (note cdus that
                            //have an order are excluded, see above!)
                            if (routeNr == 1)
                            {
                                routeOrder[i] = 1;
                                cdus.CDUs[i].RoutingOrder = routeIndex;
                                routeIndex++;
                            }
                        }
                    }
                }

                // Sort using IComparable on RoutingOrder
                cdus.CDUs.Sort();

                output = new MatrixWriter(grus.SimulationName,
                         cdus.CDUClassName,
                         config.OutputPath,
                         config.GruOutputVars,
                         config.CduOutputVars,
                         grus.GetIDs(),
                         cdus.GetIDs(),
                         config.OutputDates,
                         config.OutputThisDates);

            }
            else //gru run only
            {
                output = new MatrixWriter(grus.SimulationName,
                    config.OutputPath,
                    config.GruOutputVars,
                    grus.GetIDs(),
                    config.OutputDates,
                    config.OutputThisDates);
            }

            // gru - cdu interface
            // this dictionary provides the interface between grus and cdus
            // we could create an 'interface' directly within the gru class,
            // but using a dictionary as a 'transporter' in main is more transparent

            Dictionary<string, double> GruToCdu;
            Dictionary<string, double> gruForcing; // regionalized gru forcing

            //**************************************************************************************
            // main model loop
            //**************************************************************************************

            while (ClimateForcing.MoveAllToNextObservation())
            {
                
                GruToCdu = new Dictionary<string, double>();
                
                //check if there are any changes to gru(s). dunno of this should go here or in the loop over grus
                //it would be more intuitive if every gru stores the change info and checks it every timestep, but placing it here means
                //only one check instead of n gru checks....

                //grus.CheckForChanges(ClimateForcing.CurrentDateTime);
                // model loop over grus

                int j = 0;
                foreach(GRUClass gru in grus.GRUList)
                {
                    gruForcing = new Dictionary<string, double>();
                    gruForcing = ClimateForcing.regionalizeForcings(gru.MetStation, gru.Elevation, gru.Latitude, gru.Longitude, gru.TimeZone, gru.Slope, gru.Aspect);
                    gru.Update(ClimateForcing.CurrentDateTime, gruForcing);         
                    GruToCdu.Add(gru.GRUID, gru.WaterRelease);
                    if (gru.Write)
                    {
                        output.captureGruState(gru.FluxStateVars, j);
                        j++;
                    }
                }

                Console.WriteLine(string.Format("Current date: {0}", ClimateForcing.ClimateStations[grus.GRUList[0].MetStation].CurrentDateTime.ToString("yyyy-MM-dd HH:mm:ss")));

                //main loop over cdus (in routing order)
                if (cdus != null)
                {
                    j = 0;
                    for (int i = 0; i < cdus.CDUs.Count - 1; i++)
                    {
                        cdus.CDUs[i].Update(ClimateForcing.CurrentDateTime, GruToCdu);
                        //set lateral inflows to slow and fast storages of underlying cdu
                        cdus[cdus.CDUs[i].CDUIdDown].LateralInflowFast = cdus.CDUs[i].RunoffFast;
                        cdus[cdus.CDUs[i].CDUIdDown].LateralInflowSlow = cdus.CDUs[i].RunoffSlow;

                        //store output
                        if (cdus.CDUs[i].Write)
                        {
                            output.captureCduState(cdus.CDUs[i].FluxStateVars, j);
                            j++;
                        }
                    }
                    int outlet = cdus.CDUs.Count - 1;
                    cdus.CDUs[outlet].Update(ClimateForcing.CurrentDateTime, GruToCdu);
                    output.captureCduState(cdus.CDUs[outlet].FluxStateVars, j);
                }                 //write output
                output.write(ClimateForcing.CurrentDateTime);
            }

            output.closeAll();
        }
    }
}
